//  Copyright 2011 Google Inc. All Rights Reserved.
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

#library('isolet');

#import('dart:io');
#import('http_util.dart');
#import('dart:isolate', prefix: 'isolate');
#import('dart:json');

isolate.SendPort _sendPort;
IsoletRequest _request;

typedef _Handler(HttpRequest request, HttpResponse response);
typedef _Matcher(HttpRequest request);

class _Mount {
  _Matcher matcher;
  _Handler handler;
  
  _Mount(this.matcher, this.handler);
}

/*
 * Emulates dart:io.HttpServer
 */
class IsoletServer implements HttpServer {
  final List<_Mount> _mounts;
  _Handler _defaultHandler;
  var _errorCallback;
  
  IsoletServer() : _mounts = new List<_Mount>() {
    isolate.port.receive((Dynamic message, isolate.SendPort sendPort) {
      if (_sendPort == null) {
        _sendPort = sendPort;
      }
      _dispatch(message);
    });
  }
  
  void _dispatch(Map<String, Object> message) {
    switch (message['type']) {
      case 'request':
        _request = new IsoletRequest.fromMap(message);
        _handleRequest(_request);
        break;
      case 'onData':
        List data = message['data'];
        _request.inputStream.write(data);
        if (message['available'] == 0) {
          _request.inputStream.markEndOfStream();
        }
        break;
      case 'onClose':
        _request.inputStream.close();
        break;
      case 'onError':
        _request.inputStream.onError(message['error']);
        break;
      default:
        break;
    }
  }

  void _handleRequest(IsoletRequest req) {
    IsoletResponse res = new IsoletResponse();
    for(_Mount mount in _mounts) {
      if (mount.matcher(req)) {
        mount.handler(req, res);
        return;
      }
    }
    if (_defaultHandler != null) {
      _defaultHandler(req, res);
    }
  }
  
  void addRequestHandler(_Matcher matcher, _Handler handler) {
    _mounts.add(new _Mount(matcher, handler));
  }
  
  void set defaultRequestHandler(_Handler defaultHandler) {
    _defaultHandler = defaultHandler;
  }
  
  void close() {
    // no-op. later unload the isolate if it's cached?
  }
  
  void listen(String host, int port, [int backlog]) {
    throw "listen() is unsupprted";
  }
  
  void listenOn(ServerSocket serverSocket) {
    throw "listenOn() is unsupprted";    
  }
  
  void set onError(void callback(e)) {
    _errorCallback = callback;
  }
  
  int get port() => 0;
}

class IsoletRequest implements HttpRequest {
  final int contentLength;
  final List cookies;
  final HttpHeaders headers;
  final ListInputStream inputStream;
  final String method;
  final String path;
  final bool persistentConnection;
  final String protocolVersion;
  final Map<String, String> queryParameters;
  final String queryString;
  final String uri;
  
  IsoletRequest.fromMap(Map<String, Object> map)
    : contentLength = map['contentLength'],
      cookies = map['cookies'],
      headers = new HttpHeadersImpl.fromMap(map['headers']),
      inputStream = new ListInputStream(),
      method = map['method'],
      path = map['path'],
      persistentConnection = map['persistentConnection'],
      protocolVersion = map['protocolVersion'],
      queryParameters = map['queryParameters'],
      queryString = map['queryString'],
      uri = map['uri'];  
}

/*
 * Emulates a normal dart:io.HttpResponse but sends it's state via
 * messages to a HttpResponse in another isolate.
 * 
 * TODO: implement everything - only headers and outputStream are
 * implemented now. 
 */
class IsoletResponse implements HttpResponse {
  int contentLength;
  int statusCode;
  String reasonPhrase;
  bool persistentConnection;
  final HttpHeadersImpl headers;
  final List<Cookie> cookies;
  _IsoletResponseOutputStream _outputStream;
  
  IsoletResponse()
    : headers = new HttpHeadersImpl(),
      cookies = new List();
  
  OutputStream get outputStream() {
    if (_outputStream == null) {
      _outputStream = new _IsoletResponseOutputStream();
      // on first access of outputStream, send the headers
      _sendPort.send({'type': 'headers', 'headers': headers.map});
    }
    return _outputStream;
  }
  
  DetachedSocket detachSocket() {
    throw "detachSocket() is unsupported.";
  }
}

/*
 * Sends operations to another isolate via messages.
 */
class _IsoletResponseOutputStream implements OutputStream {
  var onClosed;
  var onError;
  var onNoPendingWrites;
  
  bool write(List buffer, [bool copyBuffer]) {
    _sendPort.send({'type': 'write', 'buffer': buffer});
  }

  bool writeFrom(List buffer, [int offset, int len]) {
    _sendPort.send({
      'type': 'writeFrom',
      'buffer': buffer,
      'offset': offset,
      'len': len});
  }
  
  bool writeString(String string, [Encoding encoding]) {
    _sendPort.send({'type': 'writeString', 'string': string});
  }
  
  void close() {
    _sendPort.send({'type': 'close'});
  }
  
  void destroy() {
    close();
  }
  
  void flush() {
    _sendPort.send({'type': 'flush'});
  }
}